﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Platform.Infrastructure;
using Platform.ObjectModel.Design;
using Platform.ObjectModel.Validation;
using System.Globalization;
using Platform.Storage;
using Platform.ObjectModel.Extended;
using System.Diagnostics;
using System.Threading;
using Platform.Resources;

namespace Platform.ObjectModel
{
	/// <summary>
	/// Biblioteca com a coleção de documentos
	/// </summary>
	public class Library
	{

		/// <summary>
		/// 
		/// </summary>
		/// <param name="storage"></param>
		public Library( ILibraryStorage storage )
		{
			Arguments.IsNotNull( "storage", storage );
			this.Storage = storage;
			this.Updater = new LibraryUpdater( this, new DefaultValidatorFactory() );

			this.Workspace = new LibrarySchemaWorkspace( this );
			// Adiciona os schemas padrão da biblioteca
			this.Workspace.Add( typeof( Schema ), Schema.CreateSchemaForDocumentSchema() );
			this.Workspace.Add( typeof( FieldDescriptor ), FieldDescriptor.CreateFieldSchema() );

			// Inicializa o storage para essa biblioteca
			storage.Init( this );
		}

		/// <summary>
		/// Obtém ou define o repositório que é utilizado para persistir os documentos
		/// </summary>
		public ILibraryStorage Storage
		{ get; private set; }

		/// <summary>
		/// Obtém ou define o gerador de tokens da biblioteca.
		/// </summary>
		/// <remarks>
		/// Se nenhum gerador for definido, o gerador <see cref="RandomTokenGeneratorStrategy"/> será utilizado
		/// </remarks>
		public ITokenGeneratorStrategy TokenGenerator
		{ get; set; }

		/// <summary>
		/// Obtém ou define as configurações do pipeline de atualização dos documentos
		/// </summary>
		public LibraryUpdater Updater
		{ get; private set; }

		/// <summary>
		/// Obtém ou define as configurações do pipeline de exclusão de documentos
		/// </summary>
		public LibraryDelete DeletePipeline
		{ get; private set; }

		/// <summary>
		/// Obtém o cache com os schemas carregados atualmente
		/// </summary>
		public LibrarySchemaWorkspace Workspace
		{ get; private set; }

		/// <summary>
		/// Registra uma nova regra na biblioteca
		/// </summary>
		/// <typeparam name="TRule"></typeparam>
		/// <param name="qualifiedName"></param>
		public void RegisterRule<TRule>( DataObjectName qualifiedName )
		{
			/**
			 * Cada regra registrada também vai ter o seu schema com as 
			 * declarações de campos que são necessárias
			 * 1. Criar outro overload ou utilizar o método "RegisterSchema( typeof( RequiredRule ), new RequiredRuleSchema() )"
			 * */
			
			throw new NotImplementedException();
		}

		/// <summary>
		/// Obtém o objeto para realizar a pesquisa no banco de dados da biblioteca
		/// </summary>
		/// <typeparam name="TData"></typeparam>
		/// <returns></returns>
		public IQueryable<TData> Query<TData>()
			where TData : DataObjectFlake
		{
			return this.Storage.Get<TData>();
			//return null;
		}

        /// <summary>
        /// Realiza a pesquisa pelos documentos do tipo <paramref name="qualifiedName"/>. É utilizado em 
        /// situações em que não se conhece o tipo do objeto <see cref="System.Type"/>, mas se tem 
        /// o nome do schema do documento. É util em pesquisas de texto livre
        /// </summary>
        /// <param name="qualifiedName"></param>
        /// <returns></returns>
        public IQueryable Query( DataObjectName qualifiedName )
        { 
            throw new NotImplementedException();
        }




		/// <summary>
		/// Faz alteração nos dados do documento no repositório
		/// </summary>
		/// <param name="document"></param>
		/// <returns></returns>
		public ValidationResult Put( DocumentFlake document )
		{
			Arguments.IsNotNull( "document", document );

			Schema schema = this.Workspace.GetSchema( document.SchemaName );

			// Garante que achou um schema para o documento
			Debug.Assert( schema != null, Exceptions.SchemaNotFound( document.SchemaName ).Message );

			// 
			return this.PutInternal( document, schema );
		}

		/// <summary>
		/// Realiza um update parcial
		/// </summary>
		/// <param name="document"></param>
		/// <param name="fields"></param>
		/// <returns></returns>
		public ValidationResult Put( DocumentFlake document, object fields )
		{
			return null;
		}

		/// <summary>
		/// Salva um documento que não possui schema
		/// </summary>
		/// <param name="document"></param>
		/// <returns></returns>
		public ValidationResult GenericPut( DocumentFlake document )
		{
			throw new NotSupportedException();
		}

		/// <summary>
		/// Salva os dados do documento
		/// </summary>
		/// <param name="document"></param>
		/// <param name="schema"></param>
		/// <returns></returns>
		internal ValidationResult PutInternal( DocumentFlake document, Schema schema )
		{
			Debug.Assert( document != null );
			Debug.Assert( schema != null );

			DocumentContext context = this.Updater.Run( document, schema );
			return context.Result;
		}

		/// <summary>
		/// Obtém o documento que foi salvo no repositório
		/// </summary>
		/// <param name="token"></param>
		/// <returns></returns>
		public DocumentFlake GetDocument( DocumentToken token )
		{
			return this.GetDocumentAs<DocumentFlake>( token );
		}

		public TDocument GetDocumentAs<TDocument>( DocumentToken token )
			where TDocument : DocumentFlake
		{
			Arguments.IsNotNull( "token", token );
			this.Storage.CheckCapability( SearchStorageCapabilities.ExactMatch );

			var query = this.Query<TDocument>();
			return query.FirstOrDefault( doc => doc.Token.Value == token.Value );
		}

		/// <summary>
		/// Remove um documento do repositório
		/// </summary>
		/// <param name="token">Identificador do documento</param>
		/// <returns></returns>
		public ValidationResult Remove( DocumentToken token )
		{
			var document = this.GetDocument( token );

			if ( document == null )
				throw Exceptions.DocumentNotFound( token );
				//throw new InvalidOperationException( "O documento não existe no repositório." );
			//var schema = this.GetSchema( document.SchemaName );
			var schema = this.Workspace.GetSchema( document.SchemaName );
			if ( schema == null )
				throw Exceptions.SchemaNotFound( document.SchemaName );
				//throw new InvalidOperationException();
			var context = this.DeletePipeline.Run( document, schema );

			return context.Result;
		}
	}

	
}
